#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <libgen.h>
#include <getopt.h>
#include <unistd.h>
#include <sys/epoll.h>

#include "check_run.h"
#include "install_signal.h"
#include "logger.h"
#include "crc-itu-t.h"
#include "server_socket.h"

#define PROG_VERSION     "1.0.0"

#define DEF_LOG_FILE     "tlv_server.log"

#define MAX_EVENTS       512

#define TAG_HEADER     0xFD
#define TAG_SN         0x02
#define TAG_TEMP       0x03
#define TAG_TIME       0x04

/* 1B Header + 1B Length + nB Data + 2B CRC */
#define TLV_FIX_SIZE   5
#define TLV_MIN_SIZE   6
#define TLV_MAX_SIZE   32

/* specify structure aligment value as 1 */
#pragma pack(1)
typedef struct tlv_pack_s
{
	unsigned char       header;
	unsigned char       tag;
	unsigned char       len;
	unsigned char       data[0];/*零长数组，简化解析时间，零长数组必须是结构体最后一个元素*/

}tlv_pack_t;
#pragma pack()

void dump_buf(char *buf, int bytes)
{
	int i;
	for(i=0; i<bytes; i++)
	{
		printf("0x%02x", buf[i]);
		if( 0 == (i+1)%16 )
			printf("\n");
	}
	printf("\n");
}

/* 打印版本信息*/
static void banner(void)
{
	printf("Version %s build on %s %s\n", PROG_VERSION, __DATE__, __TIME__); 
	printf("Copyright (C) 2020 LingYun IoT System Studio.\n\n");

	return ;
}

static void program_usage(const char *progname)
{
	printf("Usage: %s [OPTION]...\n", progname);
	printf("This program receive RPi's temperature from socket client and save it in sqlite database.\n");
	printf("\nMandatory arguments to long options are mandatory for short options too:\n");
	printf(" -p[post    ]  Specify port to listen client\n");
	printf(" -d[daemon  ]  Running in background\n");
	printf(" -l[level   ]  Set the log level as [0..%d]\n", LOG_LEVEL_MAX-1); 
	printf(" -h[help    ]  Display this help information\n"); 
	printf(" -v[version ]  Display the program version\n");
	printf("\n");

	banner();
	return ;
}

int main(int argc, char* argv[])
{
	int daemon_tag=0;
	int port=0;
	int i=0;
	int rv = 0;

	char *progname = NULL;

	int opt;  

	int log_level = LOG_LEVEL_NRML;
	char *log_file = DEF_LOG_FILE;
	logger_t              logger;

	int     listenfd,connfd;

	int     epollfd;
	struct  epoll_event event;
	struct  epoll_event event_array[MAX_EVENTS];
	int     events;

	tlv_pack_t *tlv_pack;

	unsigned short crc16=0;
	unsigned char    buf[1024];
	int on = 1;

	int    left_bytes = 0 ;


	/*   
	     命令行参数解析
	     port：指定服务器开设端口
	     deamon：程序后台运行
	     level：设定日志级别
	     help：打印帮助信息
             version:打印程序版本信息
        */

	struct option long_options[] =
	{  
		{ "port", required_argument, NULL, 'p' },
		{ "daemon", no_argument, NULL, 'd' },     
		{ "level", required_argument, NULL, 'l' },  
		{ "version", no_argument, NULL, 'v'}, 
		{ "help", no_argument, NULL, 'h'},
		{ NULL, 0, NULL, 0 },  
	};  

	progname = basename(argv[0]);
	while ((opt=getopt_long(argc, argv, "p:dl:vh", long_options, NULL)) != -1)
	{
		switch (opt)
		{
			case 'p':
				port = atoi(optarg);  /* 将字符串形式的端口号转换成整型 */
				break;

			case 'd':
				daemon_tag = 1;  
				break ;

			case 'l':   
				i = atoi(optarg); 
				log_level = i>LOG_LEVEL_MAX ? LOG_LEVEL_MAX-1 : i; 
				logger.flag |= FLAG_LOGGER_LEVEL_OPT; 
				break; 

			case 'v':  
				banner();
				return EXIT_SUCCESS; 

			case 'h':  
				program_usage(progname);
				return 0; 

			default:
				break;
		}
	}

	/* 检查端口输入是否正确 */
	if( port<=0 )
	{
		printf("ERROR: No correct argument specify port, please refer usage to input correct port\n");
		program_usage(progname);
		return 0;
	}

	/* 检查程序是否在运行 */
	if( check_run(progname) )
	{
		printf("Program is already running.\n");
		return -1;
	}

	/* 程序后台运行 */
	if (daemon_tag == 1)
	{
		if(daemon(1,1)<0)
		{
			perror("error daemon.../n"); 
			return 0;
		}
		else
		{
			printf("Program %s is running at the backgroud now\n", progname);
		}
	}
	else  if(daemon_tag == 0 )
	{
		printf("Program will enter debug mode\n");
		log_level= LOG_LEVEL_DEBUG;
		log_file = NULL;  
	}

	/* 链接日志系统 */
	if( logger_init(&logger, log_file, log_level, 512)<0 || logger_open()<0 )
	{
		printf("ERROR: Initialise logger system failure\n");
		return -1;
	}

	/* 安装信号 */
	install_proc_signal();

	log_nrml("TLV server program start running\n");

	/* 初始化socket */
	if((listenfd=server_socket_init(NULL, port)) < 0)
	{
		log_err("ERROR: %s server listen on port %d failure\n", argv[0], port);
		return -2;
	}
	log_nrml("server socket[%d] start to listen on port %d\n", listenfd, port);

	/*  创建epoll兴趣数组 */
	if((epollfd=epoll_create(MAX_EVENTS)) < 0)       
	{
		log_err("%s create epoll farilure: %s\n", argv[0], strerror(errno));
		return -3;
	}
	log_nrml("programe create epoll[%d] OK.\n", epollfd);

	/*  将listenfd加入兴趣数组 */
	event.events = EPOLLIN; 
	event.data.fd = listenfd;
	if(epoll_ctl(epollfd, EPOLL_CTL_ADD, listenfd, &event) < 0 )
	{
		log_err("epoll add listen socket[%d] failure: %s\n", listenfd, strerror(errno));
		return -4;
	}

	/*  g_signal.stop定义在install_signal.c中，用于捕捉终止信号 */
	while( !g_signal.stop )
	{   
		/*  epoll_wait等待兴趣列表的socket触发 */ 
		printf("programe will blocked here...\n");
		events=epoll_wait(epollfd, event_array, MAX_EVENTS, -1);    //成功则返回数据元素个数
		if(events < 0)
		{
			log_err("epoll failure: %s\n", strerror(errno));
			break;
		}
		else if(events==0) 
		{
			log_err("epoll timeout\n");
			continue;
		}

		/* events>0 is the active events count */
		for(i = 0; i < events; i++)
		{   
			//for循环的events与结构体成员中的events不同
			//按位与求的是结果，逻辑与求的是真或假
			/* 兴趣列表中某个socket出现错误或挂断进行清理工作 */
			if((event_array[i].events&EPOLLERR) || (event_array[i].events&EPOLLHUP)) 
			{
				log_err("epoll_wait get error on fd[%d]: %s\n",event_array[i].data.fd, strerror(errno));
				epoll_ctl(epollfd, EPOLL_CTL_DEL, event_array[i].data.fd, NULL);
				close(event_array[i].data.fd);
			}

			/* listenfd触发了，将使用accpet接受客户端的连接 */
			if(event_array[i].data.fd == listenfd) //已将listenfd加入到epoll感兴趣事件集合
			{
				if((connfd=accept(listenfd, (struct sockaddr *)NULL, NULL)) < 0) //不保存客户端信息（结构体指针为NULL）
				{
					log_nrml("accept new client failure: %s\n", strerror(errno));      
					continue;
				}

				event.data.fd = connfd; 
				event.events = EPOLLIN;

				/* 还需要将其加入epoll的兴趣列表和链表中 */
				if(epoll_ctl(epollfd, EPOLL_CTL_ADD, connfd, &event) < 0 )
				{
					log_err("epoll add client socket failure: %s\n", strerror(errno));
					close(event_array[i].data.fd);
					continue;
				}

				log_info("accept new client[%d] OK\n", connfd);           
			}

			else //data arriver from alrady connected client

			{   
				memset(buf, 0, sizeof(buf));

				if((rv=read(event_array[i].data.fd, buf, sizeof(buf))) <= 0 )  
				{
					log_warn("socket[%d] read data failure or disconnect and will be remove.\n", event_array[i].data.fd);
					epoll_ctl(epollfd, EPOLL_CTL_DEL, event_array[i].data.fd, NULL);
					close(event_array[i].data.fd);
					continue;
				}

				else /* TLV parser*/
                                					
				left_bytes=0;	
				{   
					left_bytes += rv ;
					log_dbg("socket[%d] read data: %d bytes\n", event_array[i].data.fd, left_bytes);

					log_info("start to parser data");
					logger_dump(LOG_LEVEL_INFO, buf, left_bytes);
					if(rv<=TLV_MIN_SIZE)
					{
						log_warn("TLV packet is not integrated\n");
						continue;
					}

					for(i=0; i<left_bytes; i++)
					{
						if( buf[i] == TAG_HEADER ) 
						{
							left_bytes = left_bytes -i ;
							if( left_bytes < TLV_FIX_SIZE) 
							{
								log_warn("TLV packet is not integrated\n"); 
								memmove(buf, &buf[i] , left_bytes);
								break;
							}

							tlv_pack =(tlv_pack_t *) &buf[i]; 

							/* found wrong header or CRC checksum failure */
							crc16 = crc_itu_t(MAGIC_CRC,&buf[i], tlv_pack->len-2);
							if( tlv_pack->len > TLV_MAX_SIZE || crc16 != bytes_to_ushort(&buf[i+tlv_pack->len-2], 2)) 
							{ 
								log_err("TLV packet length more than max length or CRC Wrong, maybe found wrong header?\n");
								left_bytes -= 1;
								memmove(buf, &buf[i+1] , left_bytes);
								i = 0;
								continue;

							}

							if( tlv_pack->len > left_bytes )
							{
								log_warn("TLV packet is not integrated\n"); 
								memmove(buf, &buf[i] , left_bytes); 
								break;
							}

							if ( tlv_pack->tag ==TAG_SN)
							{
								log_nrml("Found SN TLV data:\n"); 
								dump_buf(tlv_pack->data, tlv_pack->len - TLV_FIX_SIZE);
							}
							else if ( tlv_pack->tag ==TAG_TEMP)
							{
								log_nrml("Found temprature TLV data:\n"); 
								dump_buf(tlv_pack->data, tlv_pack->len - TLV_FIX_SIZE);
							}
							else if ( tlv_pack->tag ==TAG_TIME)
							{
								log_nrml("Found data time TLV data:\n"); 
								dump_buf(tlv_pack->data, tlv_pack->len - TLV_FIX_SIZE);
							}
							left_bytes -= tlv_pack ->len;
							memmove(buf, &buf[i+tlv_pack->len] , left_bytes);
							i=0;  

						}
					}

				}
			}
			sleep(1);
		} 
	}	
	logger_term();
	close(listenfd);
	return 0;


}
